package heap

import TObject
import debug.debug
import event.*
import view.OutputUtil
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.floor

class THeap(val capacity: Long, val newRatio: Int = 2, val survivorRatio: Int = 8) : EventNotifiable {
    val youngCapacity: Long = floor(capacity / (newRatio + 1.0)).toLong()
    val oldCapacity: Long = floor(capacity / (newRatio + 1.0) * newRatio).toLong()
    val edenCapacity: Long = floor(youngCapacity / (survivorRatio + 2.0) * survivorRatio).toLong()
    val survivorCapacity: Long = floor(youngCapacity / (survivorRatio + 2.0)).toLong()

    val young: MutableMap<Int, TObject> = HashMap()
    val old: MutableMap<Int, TObject> = HashMap()

    var size: Long = 0
    var userOldSize: Long = 0
    var userYoungSize: Long = 0
    var vmYoungSize = 0L
    var vmOldSize = 0L

    companion object {
        const val YOUNG = 1
        const val OLD = 2
        const val UP_AGE = 16
    }

    fun add(tObject: TObject): Int = when {
        userYoungSize + tObject.size + vmYoungSize <= edenCapacity -> {
            userYoungSize += tObject.size
            young[tObject.tag] = tObject
            YOUNG
        }
        userOldSize + tObject.size + vmOldSize <= oldCapacity -> {
            userOldSize += tObject.size
            old[tObject.tag] = tObject
            OLD
        }
        else -> throw Exception()
    }

    fun remove(tag: Int) {
        young.remove(tag)?.let {
            userYoungSize -= it.size
            return
        }
        old.remove(tag)?.let {
            userOldSize -= it.size
            return
        }
        throw Exception()
    }

    //invoke after ObjFree. move the objects, to confirm objects in correct position
    private fun minorGC(): Int {
        // Cannot use young.sumBy(TObject::size) because sumBy return Int instead of Long
        var sumSize = 0L
        young.forEach {
            sumSize += it.value.size
        }
        if (sumSize > survivorCapacity) {
            young.forEach {
                old[it.key] = it.value
            }
            val count = young.size
            young.clear()
            return count
        }

        val upList: MutableList<Int> = LinkedList()
        young.forEach {
            it.value.age++
            if (it.value.age == UP_AGE) {
                upList.add(it.key)
                old[it.key] = it.value
            }
        }
        upList.forEach { young.remove(it) }
        return upList.size
    }

    private fun fullGC() {
        when {
        //Full GC: move all alive young objects to old generation
            userOldSize + vmOldSize + vmYoungSize + userYoungSize <= oldCapacity -> {
                old.putAll(young)
                young.clear()
                userOldSize += userYoungSize
                vmOldSize += vmYoungSize
                userYoungSize = 0
                vmYoungSize = 0
            }
            else -> {
                //TODO(old generation lacks of space)
            }
        }

    }

    override fun receiveNotify(event: EventVMInit) {
        // 默认jvm初始化时所有对象在eden区
        vmYoungSize = event.heapSize
        val bean = DataBean(BeanType.VMINIT, jvmObjYoungSize = vmYoungSize)
        OutputUtil.add(bean)
        "receiveNotify EventVMInit: $bean".debug()
    }

    override fun receiveNotify(event: EventObjectAlloc) {
        val obj = TObject(event.tag, event.signature, event.size)
        obj.position = add(obj)
        val bean = DataBean(BeanType.OBJECTALLOC, tag = obj.tag, objSignature = obj.signature, objSize = obj.size, objPosition = obj.position.toPosition())

        OutputUtil.add(bean)
        "receiveNotify receiveNotify: $bean".debug()
    }

    override fun receiveNotify(event: EventObjectFree) {
        val obj = TObject(event.tag, event.signature, -1)
        remove(event.tag)
        val bean = DataBean(BeanType.OBJECTFREE, tag = obj.tag, objPosition = obj.position.toPosition())
        OutputUtil.add(bean)
        "receiveNotify EventObjectFree: $bean".debug()


    }

    override fun receiveNotify(event: EventGCStart) {
        val bean = DataBean(BeanType.GCSTART)
        OutputUtil.add(bean)
        "receiveNotify EventGCStart: $bean".debug()

    }

    override fun receiveNotify(event: EventGCFinish) {
        val bean = DataBean(BeanType.GCFINISH)
        OutputUtil.add(bean)
        "receiveNotify EventGCFinish: $bean".debug()

    }

    // 用于计算jvm对象占用的空间
    override fun receiveNotify(event: EventFullGC) {
        // to predicate vmObjects in young and old
        fullGC()
        vmYoungSize = (event.youngAfter - userYoungSize)//.onlyPositive()
        vmOldSize = (event.oldAfter - userOldSize)//.onlyPositive()
        val youngList = ArrayList<TObject>().apply {
            addAll(young.values)
        }
        val oldList = ArrayList<TObject>().apply {
            addAll(old.values)
        }
        val bean = DataBean(BeanType.FullGC, youngObjs = youngList, oldObjs = oldList, jvmObjYoungSize = vmYoungSize, jvmObjOldSize = vmOldSize)
        OutputUtil.add(bean)
        "receiveNotify EventFullGC: $bean".debug()

    }

    // 用于计算jvm对象占用的空间
    override fun receiveNotify(event: EventMinorGC) {
        // to predicate vmObjects in young
        minorGC()
        vmYoungSize = (event.youngAfter - userYoungSize)//.onlyPositive()
        vmOldSize = (event.heapAfter - event.youngAfter - userOldSize)
        val youngList = ArrayList<TObject>().apply {
            addAll(young.values)
        }
        val oldList = ArrayList<TObject>().apply {
            addAll(old.values)
        }
        val bean = DataBean(BeanType.MINORGC, youngObjs = youngList, oldObjs = oldList, jvmObjYoungSize = vmYoungSize, jvmObjOldSize = vmOldSize)
        OutputUtil.add(bean)
        "receiveNotify EventMinorGC: $bean".debug()
    }

}

fun Int.toPosition(): String? {
    when (this) {
        THeap.YOUNG -> return "Young"
        THeap.OLD -> return "Old"
    }
    return null
}

fun Long.onlyPositive(): Long = if (this >= 0) this else 0
